﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Dtd.Schema;
using Engine.Sgml.Configuration;

namespace Engine.Sgml.Validation {
    internal sealed class RootElementValidator : IElementValidator {
        private SgmlDocument document;

        public RootElementValidator(SgmlDocument document) {
            Guard.ArgumentNotNull(document, "document");
            this.document = document;
            }

        public SElement Validate(SElement element, SElement node) {
            // Если документ пустой, не содержит ни одного узла
            // завершить проверку, т.к. нет структуры
            if (element.Nodes.Count == 0)
                return element;
            // Получить корневой узел из документа
            SElement root = GetRootElement(element);
            // Если корневой узел не найден,
            // построить его
            if (root == null)
                BuildRootElement(element, out root);
            else {
                int startIndex = root.OrderIndex - 1;
                int endIndex = 0;
                SElement next = element.Nodes.FirstElement;
                while (next != null &&
                    next.NodeType == SgmlNodeType.DocumentType ||
                    next.NodeType == SgmlNodeType.ProcessingInstruction) {
                    endIndex++;
                    next = next.NextNode;
                    }
                // Разместить узлы находящиеся до корневого узла
                while (startIndex >= endIndex) {
                    root.AddFirst(element.Nodes[startIndex--]);
                    }
                // Разместить узлы находящиеся после корневого узла
                startIndex = root.OrderIndex + 1;
                while (startIndex != element.Nodes.Count) {
                    root.Add(element.Nodes[startIndex]);
                    }
                }
            return root;
            }

        private void BuildRootElement(SElement top, out SElement root) {
            DtdSchema schema = document.Schema;
            if (document.DocumentType == SgmlDocumentType.Xml && document.IsSchemaInternal) {
                root = top.Nodes.Push();
                root.Clear();
                SElement node = top.Nodes.FirstElement;
                while (node != null) {
                    if (node.NodeType == SgmlNodeType.DocumentType) {
                        root.Name = node.LocalName;
                        break;
                        }
                    node = node.NextNode;
                    }
                if (node == null)
                    root.Name = schema.Name.ToString();
                root.NodeType = SgmlNodeType.Element;
                root.Parent = top;
                root.LineNumber = 0;
                root.LinePosition = 0;

                document.TraceSource.TraceConstractedElement(root, top);
                }
            else {
                root = top.Nodes.Push();
                root.Clear();
                root.Name = schema.Name.ToString();
                root.DName = schema.Name;
                root.Declaration = schema.RootDeclaration;
                root.NodeType = SgmlNodeType.Element;
                root.Parent = top;

                root.LineNumber = 0;
                root.LinePosition = 0;

                // Добавить все узлы документа в корневой узел
                // кроме узла тип документа
                int startIndex = 0;
                if (top.Nodes.FirstElement.NodeType == SgmlNodeType.DocumentType)
                    startIndex = 1;
                while (startIndex < top.Nodes.Count - 1) {
                    root.Add(top.Nodes[startIndex]);
                    }
                }
            }

        private SElement GetRootElement(SElement top) {
            if (document.ValidatorBehavior.DtdDisabled || (document.DocumentType == SgmlDocumentType.Xml && document.IsSchemaInternal)) {
                SElement root = top.Nodes.FirstElement;
                while (root != null && root.NodeType != SgmlNodeType.Element) {
                    root = root.NextNode;
                    }
                return root;
                }
            else
                return top.Nodes[document.Schema.Name];
            }
        }
    }
